#include <stdio.h>
#include <stdbool.h>
#include <pjmedia/errno.h>
#include "od_sip_sdp.h"
#include "od_trace.h"

#define OD_GET_FMTP_IVAL_BASE(ival, base, fmtp, param, default_val) \
    do { \
	pj_str_t s; \
	char *p; \
	p = pj_stristr(&fmtp.fmt_param, &param); \
	if (!p) { \
	    ival = default_val; \
	    break; \
	} \
	pj_strset(&s, p + param.slen + 1, fmtp.fmt_param.slen - \
		  (p - fmtp.fmt_param.ptr) - param.slen - 1); \
	ival = pj_strtoul2(&s, NULL, base); \
    } while (0)

#define OD_GET_FMTP_IVAL(ival, fmtp, param, default_val) \
	        OD_GET_FMTP_IVAL_BASE(ival, 10, fmtp, param, default_val)

#define THIS_FILE   "od_sip_sdp.c"

/* Common initialization for both audio and video SDP media line */
static pj_status_t init_sdp_media(pjmedia_sdp_media *m,
                                   pj_pool_t *pool,
                                   const pj_str_t *media_type,
                                   const od_media_config *local_param)
{
    pjmedia_sdp_attr *attr;

    pj_strdup(pool, &m->desc.media, media_type);

    /* Validate address family */
    PJ_ASSERT_RETURN(local_param->net_type == OD_SIP_NET_IPV4 ||
                     local_param->net_type == OD_SIP_NET_IPV6,
                     PJ_EINVAL);

    /* SDP connection line */
    m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
    m->conn->net_type = STR_IN;
    m->conn->addr_type = (local_param->net_type == OD_SIP_NET_IPV4)? STR_IP4:STR_IP6;
    pj_strdup2(pool, &m->conn->addr, local_param->ipaddr);

    /* Port and transport in media description */
    if (!pj_strcmp(&STR_VIDEO, media_type))
        m->desc.port = local_param->video_recv_port;
    else if(!pj_strcmp(&STR_AUDIO, media_type))
        m->desc.port = local_param->audio_recv_port;
    m->desc.port_count = 1;
    pj_strdup (pool, &m->desc.transport, &STR_RTP_AVP);

    /* TODO: Add "rtcp" attribute */

    /* Add sendrecv attribute. */
    attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
    attr->name = STR_SENDRECV;
    m->attr[m->attr_count++] = attr;

    return PJ_SUCCESS;
}

static pj_status_t build_audio_media_rtpmap_fmtp(pj_pool_t *pool,
                                             pjmedia_sdp_media *m,
                                             od_sip_audio_media_type aud_codec_type) 
{
    const pjmedia_codec_info *codec_info = NULL;
    const pjmedia_codec_fmtp *codec_fmtp = NULL;
    pjmedia_sdp_rtpmap rtpmap;
    char tmp_param[3];
    pj_str_t *fmt;
    pjmedia_sdp_attr *attr = NULL;
    int idx;

    for (idx=0; idx < PJ_ARRAY_SIZE(g_audio_desc); idx++) {
        if (aud_codec_type == g_audio_desc[idx].media_type) {
            codec_info = &g_audio_desc[idx].codec_desc;
            codec_fmtp = &g_audio_desc[idx].codec_fmtp;
            break;
        } 
    }

    PJ_ASSERT_RETURN(codec_info && codec_fmtp, PJ_EINVAL);

    fmt = &m->desc.fmt[m->desc.fmt_count++];
    fmt->ptr = (char*) pj_pool_alloc(pool, 8);
    fmt->slen = pj_utoa(codec_info->pt, fmt->ptr);

    rtpmap.pt = *fmt;
    rtpmap.enc_name = codec_info->encoding_name;
    rtpmap.clock_rate = codec_info->clock_rate;

    /* For audio codecs, rtpmap parameters denotes the number
     * of channels, which can be omited if the value is 1.
     */
    if (codec_info->type == PJMEDIA_TYPE_AUDIO
            && codec_info->channel_cnt > 1)
    {
        /* Can only support one digit channel count */
        pj_assert(codec_info->channel_cnt < 10);

        tmp_param[0] = (char)('0' + codec_info->channel_cnt);

        rtpmap.param.ptr = tmp_param;
        rtpmap.param.slen = 1;

    } else {
        rtpmap.param.ptr = (char *)"";
        rtpmap.param.slen = 0;
    }

    /* Add rtpmap to this media attrbrute */
    pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr);
    m->attr[m->attr_count++] = attr;

    /* Add fmtp params */
    if (codec_fmtp->cnt > 0) {
        enum { MAX_FMTP_STR_LEN = 160 };
        char buf[MAX_FMTP_STR_LEN];
        unsigned buf_len = 0, ii;
        const pjmedia_codec_fmtp *dec_fmtp = codec_fmtp;

        /* Print codec PT */
        buf_len += pj_ansi_snprintf(buf,
                                    MAX_FMTP_STR_LEN - buf_len,
                                    "%d",
                                    codec_info->pt);

        for (ii = 0; ii < dec_fmtp->cnt; ++ii) {
            pj_size_t test_len = 2;

            /* Check if buf still available */
            test_len = dec_fmtp->param[ii].val.slen + 
            dec_fmtp->param[ii].name.slen + 2;
            if (test_len + buf_len >= MAX_FMTP_STR_LEN)
                return PJ_ETOOBIG;

            /* Print delimiter */
            buf_len += pj_ansi_snprintf(&buf[buf_len], 
                                        MAX_FMTP_STR_LEN - buf_len,
                                        (ii == 0?" ":";"));

            /* Print an fmtp param */
            if (dec_fmtp->param[ii].name.slen)
                buf_len += pj_ansi_snprintf(&buf[buf_len],
                                            MAX_FMTP_STR_LEN - buf_len,
                                            "%.*s=%.*s",
                                            (int)dec_fmtp->param[ii].name.slen,
                                            dec_fmtp->param[ii].name.ptr,
                                            (int)dec_fmtp->param[ii].val.slen,
                                            dec_fmtp->param[ii].val.ptr);
            else
                buf_len += pj_ansi_snprintf(&buf[buf_len], 
                                            MAX_FMTP_STR_LEN - buf_len,
                                            "%.*s", 
                                            (int)dec_fmtp->param[ii].val.slen,
                                            dec_fmtp->param[ii].val.ptr);
        }

        attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
        attr->name = pj_str((char *)"fmtp");
        attr->value = pj_strdup3(pool, buf);
        m->attr[m->attr_count++] = attr;
        
    }

    return PJ_SUCCESS;
}

static int build_audio_media_attr(pj_pool_t *pool,
                                    pjmedia_sdp_media *m,
                                    const od_media_config *local_param)
{
    int idx;
    int aud_codec_num = 0;

    for (idx = 0; idx < PJ_ARRAY_SIZE(g_audio_desc); idx++)
    {
        if (local_param->audio_codec & g_audio_desc[idx].media_type) {
            if (build_audio_media_rtpmap_fmtp(pool, m, g_audio_desc[idx].media_type) == PJ_SUCCESS) {
                aud_codec_num++;
            }
        }
    }

    return aud_codec_num;
}

static pj_status_t build_h264_vid_codec_fmtp(pj_pool_t *pool,
                                        pjmedia_codec_fmtp *codec_param,
                                        const od_media_config *local_param)
{
    pj_str_t *name;
    pj_str_t *val;
    const od_h264_level_info_t *p_h264 = &h264_standard_table[0];
    int idx;

    for (idx=0; idx < PJ_ARRAY_SIZE(h264_standard_table); idx++)
    {
        if (p_h264[idx].reso.w == local_param->reso.w
             && p_h264[idx].reso.h == local_param->reso.h
             && p_h264[idx].fps == local_param->reso.fps)
             break;
    }

    if (idx >= PJ_ARRAY_SIZE(h264_standard_table)) {
        RPTERR("idx >= PJ_ARRAY_SIZE(h264_standard_table) error!");
        return -1;
    }

    /* Add profile-level-id */
    name = &codec_param->param[codec_param->cnt].name;
    pj_strdup(pool, name, &STR_PROFILE_LEVEL_ID);
    val  = &codec_param->param[codec_param->cnt++].val;
    pj_cstr(val, p_h264[idx].profile_level_id);
    
    /* Add max-mbps */
    name = &codec_param->param[codec_param->cnt].name;
    pj_strdup(pool, name, &STR_MAX_MBPS);
    val  = &codec_param->param[codec_param->cnt++].val;
    val->ptr = (char *)pj_pool_alloc(pool, 32);
    val->slen = pj_utoa(p_h264[idx].max_mbps, val->ptr);

    /* Add max-fs */
    name = &codec_param->param[codec_param->cnt].name;
    pj_strdup(pool, name, &STR_MAX_FS);
    val  = &codec_param->param[codec_param->cnt++].val;
    val->ptr = (char *)pj_pool_alloc(pool, 32);
    val->slen = pj_utoa(p_h264[idx].max_fs, val->ptr);
    
    return codec_param->cnt;
}

static pj_status_t build_h265_vid_codec_fmtp(pj_pool_t *pool,
                                        pjmedia_codec_fmtp *codec_param,
                                        const od_media_config *local_param)
{
    pj_str_t *name;
    pj_str_t *val;
    const od_h265_level_info_t *p_h265 = &h265_standard_table[0];
    int idx;

    for (idx=0; idx < PJ_ARRAY_SIZE(h265_standard_table); idx++)
    {
        if (p_h265[idx].reso.w == local_param->reso.w
             && p_h265[idx].reso.h == local_param->reso.h
             && p_h265[idx].fps == local_param->reso.fps)
             break;
    }

    if (idx >= PJ_ARRAY_SIZE(h265_standard_table)) {
        RPTERR("idx >= PJ_ARRAY_SIZE(h264_standard_table) error!");
        return PJ_ENOTFOUND;
    }

    /* Add profile-level-id */
    name = &codec_param->param[codec_param->cnt].name;
    pj_strdup(pool, name, &STR_PROFILE_LEVEL_ID);
    val  = &codec_param->param[codec_param->cnt++].val;
    pj_cstr(val, p_h265[idx].profile_level_id);
    
    /* Add max-mbps */
    name = &codec_param->param[codec_param->cnt].name;
    pj_strdup(pool, name, &STR_MAX_LSR);
    val  = &codec_param->param[codec_param->cnt++].val;
    val->ptr = (char *)pj_pool_alloc(pool, 32);
    val->slen = pj_utoa(p_h265[idx].max_lsr, val->ptr);

    /* Add max-fs */
    name = &codec_param->param[codec_param->cnt].name;
    pj_strdup(pool, name, &STR_MAX_LPS);
    val  = &codec_param->param[codec_param->cnt++].val;
    val->ptr = (char *)pj_pool_alloc(pool, 32);
    val->slen = pj_utoa(p_h265[idx].max_lps, val->ptr);

    /*FIXME: just for text, Add packetization-mode */
    name = &codec_param->param[codec_param->cnt].name;
    pj_strdup(pool, name, &STR_PACKETIZATION_MODE);
    val  = &codec_param->param[codec_param->cnt++].val;
    val->ptr = (char *)pj_pool_alloc(pool, 32);
    val->slen = pj_utoa(1, val->ptr);
    
    return codec_param->cnt;
}

static pj_status_t build_javs_vid_codec_fmtp(pj_pool_t *pool,
                                        pjmedia_codec_fmtp *codec_param,
                                        const od_media_config *local_param)
{
    /*TODO*/
    return PJ_SUCCESS;
}

static pj_status_t build_vid_media_rtpmap_fmtp(od_sip_ctx *ctx,
                                           pjmedia_sdp_media *m,
                                           const od_media_config *local_param,
                                           od_sip_video_media_type vid_codec_type)
{
    pjmedia_sdp_rtpmap rtpmap;
    pjmedia_sdp_attr *attr;
    pjmedia_codec_fmtp codec_param;
    pj_str_t *fmt = NULL;

    if (m->desc.fmt_count > (PJMEDIA_MAX_SDP_FMT - 1)){
        /* Too many codec, perhaps it is better to tell application by
        * return appropriate status code
        */
        RPTERR("Too many codecs[%d] > [%d]!!Skipping some video codecs\r\n",
                     m->desc.fmt_count, (PJMEDIA_MAX_SDP_FMT - 1));
        return PJ_ETOOMANY;
    }

    pj_bzero(&rtpmap, sizeof(rtpmap));

    /*add a new pt to m= line tail*/
    fmt = &m->desc.fmt[m->desc.fmt_count++];
    fmt->ptr = (char *)pj_pool_alloc(ctx->pool, 8);
    if (vid_codec_type == OD_SIP_VID_H264)
        fmt->slen = pj_utoa(local_param->vid_h264_pt, fmt->ptr);
    else if (vid_codec_type == OD_SIP_VID_H265)
        fmt->slen = pj_utoa(local_param->vid_h265_pt, fmt->ptr);
    else if (vid_codec_type == OD_SIP_VID_JAVS)
        fmt->slen = pj_utoa(local_param->vid_javs_pt, fmt->ptr);
        
    rtpmap.pt = *fmt;

    /* Encoding name */
    if (vid_codec_type == OD_SIP_VID_H264)
        rtpmap.enc_name = pj_str( (char *)"H264");
    else if (vid_codec_type == OD_SIP_VID_H265)
        rtpmap.enc_name = pj_str( (char *)"H265");
    else if (vid_codec_type == OD_SIP_VID_JAVS)
        rtpmap.enc_name = pj_str( (char *)"JAVS");
    else {
        rtpmap.enc_name = pj_str( (char *)"unknown_vid_codec");
        return PJ_ERESOLVE;
    }

    /* Clock rate */
    rtpmap.clock_rate = 90000;

    /* Add rtpmap to media attr */
    pjmedia_sdp_rtpmap_to_attr(ctx->pool, &rtpmap, &attr);
    m->attr[m->attr_count++] = attr;


    /**
    * GJB1.0 need add fmtp to media attr
    */
    if (ctx->engine_cfg.protocol_standard == OD_SIP_PROTOCOL_GJB10) {
        pj_bzero(&codec_param, sizeof(codec_param));
        if (vid_codec_type == OD_SIP_VID_H264)
            build_h264_vid_codec_fmtp(ctx->pool, &codec_param, local_param);
        else if (vid_codec_type == OD_SIP_VID_H265)
            build_h265_vid_codec_fmtp(ctx->pool, &codec_param, local_param);
        else if (vid_codec_type == OD_SIP_VID_JAVS)
            build_javs_vid_codec_fmtp(ctx->pool, &codec_param, local_param);
        else {
            RPTERR("unsupport media type: %d", vid_codec_type);
            return PJ_ERESOLVE;
        }

        if (codec_param.cnt > 0){
            enum { MAX_FMTP_STR_LEN = 160 };
            char buf[MAX_FMTP_STR_LEN];
            unsigned buf_len = 0, j;
            pjmedia_codec_fmtp *dec_fmtp = &codec_param;

            /* Print codec PT */
            buf_len += pj_ansi_snprintf(buf,
                                        MAX_FMTP_STR_LEN - buf_len,
                                        "%.*s",
                                        (int)rtpmap.pt.slen,
                                        rtpmap.pt.ptr);
            
            for (j = 0; j < dec_fmtp->cnt; ++j) {
                pj_size_t test_len = 2;

                /* Check if buf still available */
                test_len = dec_fmtp->param[j].val.slen + 
                           dec_fmtp->param[j].name.slen + 2;
                if (test_len + buf_len >= MAX_FMTP_STR_LEN)
                    return PJ_ETOOBIG;

                /* Print delimiter */
                buf_len += pj_ansi_snprintf(&buf[buf_len],
                                            MAX_FMTP_STR_LEN - buf_len,
                                            (j == 0?" ":";"));

                /* Print an fmtp param */
                if (dec_fmtp->param[j].name.slen) {
                    buf_len += pj_ansi_snprintf(&buf[buf_len],
                                                MAX_FMTP_STR_LEN - buf_len,
                                                "%.*s=%.*s",
                                                (int)dec_fmtp->param[j].name.slen,
                                                dec_fmtp->param[j].name.ptr,
                                                (int)dec_fmtp->param[j].val.slen,
                                                dec_fmtp->param[j].val.ptr);
                }else {
                    buf_len += pj_ansi_snprintf(&buf[buf_len],
                                                MAX_FMTP_STR_LEN - buf_len,
                                                "%.*s",
                                                (int)dec_fmtp->param[j].val.slen,
                                                dec_fmtp->param[j].val.ptr);
                }            
            }
            
            attr = PJ_POOL_ALLOC_T(ctx->pool, pjmedia_sdp_attr);
            attr->name = pj_str( (char *)"fmtp");
            attr->value = pj_strdup3(ctx->pool, buf);
            m->attr[m->attr_count++] = attr;
        }
    }


    /*TODO: addr TIAS attr*/

    return PJ_SUCCESS;
}

static char* build_GB28181_f(od_sip_ctx *ctx, const od_media_config *local_param)
{
    static char f[32] = {0};

    PJ_UNUSED_ARG(ctx);
    
    /*video*/
    od_sip_gb28181_vid_codec vid_codec = OD_GB28181_CODEC_VID_H264;
    od_sip_gb28181_reso reso = OD_GB28181_RESO_D1;
    od_sip_gb28181_bitrate_type bitrate_type = OD_GB28181_BITRATE_CBR;
    uint32_t fps = 25;
    uint32_t vid_bitrate = 1024000;
    /*audio*/
    od_sip_gb28181_aud_codec aud_codec = OD_GB28181_CODEC_AUD_G711;
    od_sip_gb28181_aud_bitrate aud_bitrate = OD_GB28181_AUD_BR_64K;
    od_sip_gb28181_aud_samplerate aud_samplerate = OD_GB28181_AUD_SAMPLERATE_16K;

    /***********************************************
    *   Video parameters
    ***********************************************/
    /*create vide_codec*/
    if (local_param->video_codec & OD_SIP_VID_H264) {
        vid_codec = OD_GB28181_CODEC_VID_H264;
    } else if (local_param->video_codec & OD_SIP_VID_3GP) {
        vid_codec = OD_GB28181_CODEC_VID_3GP;
    } else if (local_param->video_codec & OD_SIP_VID_MPEG4) {
        vid_codec = OD_GB28181_CODEC_VID_MPEG4;
    } else if (local_param->video_codec & OD_SIP_VID_SVAC) {
        vid_codec = OD_GB28181_CODEC_VID_SVAC;
    }

    /*convert reso to GB28181 style*/
    if (local_param->reso.w == 176 && local_param->reso.h == 144) {
        reso = OD_GB28181_RESO_QCIF;
    } else if (local_param->reso.w == 352 && local_param->reso.h == 288) {
        reso = OD_GB28181_RESO_CIF;
    } else if (local_param->reso.w == 704 && local_param->reso.h == 576) {
        reso = OD_GB28181_RESO_4CIF;
    } else if (local_param->reso.w == 720 && local_param->reso.h == 576) {
        reso = OD_GB28181_RESO_D1;
    } else if (local_param->reso.w == 1280 && local_param->reso.h == 720) {
        reso = OD_GB28181_RESO_720P;
    } else if (local_param->reso.w == 1920 && local_param->reso.h == 1080) {
        reso = OD_GB28181_RESO_1080P_I;
    }
    
    fps = local_param->reso.fps;
    bitrate_type = local_param->bitrate_type; 
    vid_bitrate = local_param->vid_bitrate;

    /***********************************************
    *   Audio parameters
    ***********************************************/
    if (local_param->audio_codec & OD_SIP_AUD_G711A_8K) {
        aud_codec = OD_GB28181_CODEC_AUD_G711;
        aud_bitrate = OD_GB28181_AUD_BR_64K;
        aud_samplerate = OD_GB28181_AUD_SAMPLERATE_8K;
    } else if (local_param->audio_codec & OD_SIP_AUD_G723) {
        aud_codec = OD_GB28181_CODEC_AUD_G723_1;
        aud_bitrate = OD_GB28181_AUD_BR_6_3K;
        aud_samplerate = OD_GB28181_AUD_SAMPLERATE_8K;
    } else if (local_param->audio_codec & OD_SIP_AUD_G729A) {
        aud_codec = OD_GB28181_CODEC_AUD_G729;
        aud_bitrate = OD_GB28181_AUD_BR_8K;
        aud_samplerate = OD_GB28181_AUD_SAMPLERATE_8K;
    } else if (local_param->audio_codec & OD_SIP_AUD_G722) {
        aud_codec = OD_GB28181_CODEC_AUD_G722_1;
        aud_bitrate = OD_GB28181_AUD_BR_16K;
        aud_samplerate = OD_GB28181_AUD_SAMPLERATE_16K;
    } 
    
    snprintf(f, sizeof(f), "v/%u/%u/%u/%u/%ua/%u/%u/%u", vid_codec,
                                                         reso,
                                                         fps,
                                                         bitrate_type,
                                                         vid_bitrate,
                                                         aud_codec,
                                                         aud_bitrate,
                                                         aud_samplerate);

    return f;
}

static char* build_GB28181_y(od_sip_ctx *ctx, const od_media_config *local_param)
{
    static char y[32] = {0};
    
    PJ_UNUSED_ARG(ctx);

    /*FIXME: 需要根据local_param参数进行y构建*/
    snprintf(y, sizeof(y), "1000102002");
    return y;
}

static int build_video_media_attr(od_sip_ctx *ctx, 
                                     pjmedia_sdp_media *m,
                                     const od_media_config *local_param)
{
    pjmedia_sdp_bandw *b;
    int vid_codec_num = 0;
    
    if (local_param->video_codec & OD_SIP_VID_H264) {
        if (build_vid_media_rtpmap_fmtp(ctx, m, local_param, OD_SIP_VID_H264)
             != PJ_SUCCESS) {
            RPTERR("build H264 rtpmap & fmtp error!!!");
        }else {
            vid_codec_num++;
        }
    }    

    if (local_param->video_codec & OD_SIP_VID_H265) {
        if (build_vid_media_rtpmap_fmtp(ctx, m, local_param, OD_SIP_VID_H265)
              != PJ_SUCCESS) {
            RPTERR("build H265 rtpmap & fmtp error!!!");
        }else {
            vid_codec_num++;
        }
    } 

    /*TODO: need JAVS sdp description define!!!*/
    //if (local_param->video_codec & OD_SIP_VID_JAVS)
    //    build_vid_media_rtpmap_fmtp(pool, m, local_param, OD_SIP_VID_JAVS);

    /* Put bandwidth info in media level using bandwidth modifier "TIAS"
     * (RFC3890).
     */
    b = PJ_POOL_ALLOC_T(ctx->pool, pjmedia_sdp_bandw);
    b->modifier = STR_BANDW_TIAS;
    b->value = local_param->vid_bitrate;
    m->bandw[m->bandw_count++] = b;

    if (ctx->engine_cfg.protocol_standard == OD_SIP_PROTOCOL_GB28181_2016) {
        pj_strdup2(ctx->pool, &m->y, build_GB28181_y(ctx, local_param));
        pj_strdup2(ctx->pool, &m->f, build_GB28181_f(ctx, local_param));
    }

    return vid_codec_num;
}


static pj_status_t create_base_sdp(pj_pool_t *pool,
                               const pj_str_t *sess_name,
                               pjmedia_sdp_session **p_sdp,
                               const od_media_config *local_param)
{
    pjmedia_sdp_session *sdp = NULL;
    pjmedia_sdp_attr *attr = NULL;
    pjmedia_sdp_conn *conn = NULL;
    const pj_str_t STR_ERROR = { (char *)"err_type", 8 };
    pj_time_val tv;

    /*Sanity check argauments*/
    PJ_ASSERT_RETURN(pool && p_sdp && local_param, PJ_EINVAL);

    sdp = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session);

    /*create Session origin (o= line)*/
    pj_gettimeofday(&tv);
    sdp->origin.user = STR_SDP_NAME;
    sdp->origin.id = sdp->origin.version = tv.sec + 2208988800UL;
    sdp->origin.net_type = STR_IN;

    /*FIXME*/
    if (local_param->net_type == OD_SIP_NET_IPV4)
        sdp->origin.addr_type = STR_IP4;
    else if (local_param->net_type == OD_SIP_NET_IPV6)
        sdp->origin.addr_type = STR_IP6;
    else {
        RPTERR("unknown addr_type!!!");
        sdp->origin.addr_type = STR_ERROR;
        return PJ_ERESOLVE;
    }
    pj_strdup2(pool, &sdp->origin.addr, local_param->ipaddr);

    /*session name (s=)*/
    if (sess_name)
        pj_strdup(pool, &sdp->name, sess_name);
    else
        sdp->name = STR_SDP_NAME;
    
    /* SDP time and attributes. */
    sdp->time.start = sdp->time.stop = 0;

    /* Add Connection line (c=)*/
    conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
    conn->net_type = STR_IN;
    if (local_param->net_type == OD_SIP_NET_IPV4)
        conn->addr_type = STR_IP4;
    else if (local_param->net_type == OD_SIP_NET_IPV6)
        conn->addr_type = STR_IP6;
    else {
        RPTERR("unknown addr_type!!!");
        conn->addr_type = STR_ERROR;
        return PJ_ERESOLVE;
    }
    pj_strdup2(pool, &conn->addr, local_param->ipaddr);
    sdp->conn = conn;

    /* Add rtpport-mux attr */
    if (local_param->rtpport_mux.flg == true)
    {
        attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
        attr->name = STR_RTP_PORTMUX;
        sdp->attr[sdp->attr_count++] = attr;

        attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr);
        attr->name = STR_MUXID;
        attr->value.ptr = (char *)pj_pool_alloc(pool, 16);
        attr->value.slen = pj_utoa(local_param->rtpport_mux.muxid, attr->value.ptr);
        sdp->attr[sdp->attr_count++] = attr;
    }
   
    /* Done */
    *p_sdp = sdp;

    return PJ_SUCCESS;  
}


static pj_status_t create_initial_audio_sdp(pj_pool_t *pool,
                                         pjmedia_sdp_media **p_m,
                                         const od_media_config *local_param)
{
    pjmedia_sdp_media *m = NULL;
    pj_status_t status;
    
    /*Create and init base SDP media*/
    m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
    status = init_sdp_media(m, pool, &STR_AUDIO, local_param);
    if (status != PJ_SUCCESS)
        return PJ_EINVAL;

    /* Add format, rtpmat, and fmtp for each codec */
    if (build_audio_media_attr(pool, m, local_param) == 0) {
        RPTWRN("SDP create with no Audio codec!!");
        return PJ_EINVAL;
    }
    
    *p_m = m;
    return PJ_SUCCESS;
}


static pj_status_t create_initial_video_sdp(od_sip_ctx *ctx,
                                          pjmedia_sdp_media **p_m,
                                          const od_media_config *local_param)
{
    pjmedia_sdp_media *m = NULL;
    pj_status_t status;
    
    /* Create and init basic SDP media */
    m = PJ_POOL_ZALLOC_T(ctx->pool, pjmedia_sdp_media);
    status = init_sdp_media(m, ctx->pool, &STR_VIDEO, local_param);
    if (status != PJ_SUCCESS)
        return PJ_EINVAL;

    /* Add format, rtpmat, and fmtp for each codec */
    if (build_video_media_attr(ctx, m, local_param) == 0) {
        RPTWRN("SDP create with no Video Codec!!");
        return PJ_EINVAL;
    }

    *p_m = m;
    return PJ_SUCCESS;
}


static pj_status_t media_create_audio_capa(pj_pool_t *pool,
                                    pjmedia_sdp_media **p_m,
                                    const od_media_config *local_param)
{
    return create_initial_audio_sdp(pool, p_m, local_param);
}

static pj_status_t media_create_video_capa(od_sip_ctx *ctx,
                                     pjmedia_sdp_media **p_m,
                                     const od_media_config *local_param)
{
    pjmedia_sdp_media *m = NULL;
    pjmedia_sdp_bandw *b;
    od_media_config capa_param;
    pj_int32_t idx;
    pj_status_t status;
    pj_int32_t pt_begin = 96;

    /* Create and init basic SDP media */
    m = PJ_POOL_ZALLOC_T(ctx->pool, pjmedia_sdp_media);
    status = init_sdp_media(m, ctx->pool, &STR_VIDEO, local_param);
    if (status != PJ_SUCCESS)
       return PJ_EINVAL;

    memcpy(&capa_param, local_param, sizeof(od_media_config));

    if (ctx->engine_cfg.protocol_standard == OD_SIP_PROTOCOL_GJB10) {
        if (local_param->video_codec & OD_SIP_VID_H264) {
            for (idx = 0; idx < PJ_ARRAY_SIZE(h264_standard_table); idx++)
            {
                capa_param.reso.w = h264_standard_table[idx].reso.w;
                capa_param.reso.h = h264_standard_table[idx].reso.h;
                capa_param.reso.fps = h264_standard_table[idx].fps;
                capa_param.vid_h264_pt = pt_begin++;
                build_vid_media_rtpmap_fmtp(ctx, m, &capa_param, OD_SIP_VID_H264);
            }
        }
           
        if (local_param->video_codec & OD_SIP_VID_H265) {
            for (idx = 0; idx < PJ_ARRAY_SIZE(h265_standard_table); idx++)
            {
                capa_param.reso.w = h265_standard_table[idx].reso.w;
                capa_param.reso.h = h265_standard_table[idx].reso.h;
                capa_param.reso.fps = h265_standard_table[idx].fps;
                capa_param.vid_h265_pt = pt_begin++;
                build_vid_media_rtpmap_fmtp(ctx, m, &capa_param, OD_SIP_VID_H265);
            }
        } 

        /*TODO: need JAVS sdp description define!!!*/
        //if (local_param->video_codec & OD_SIP_VID_JAVS)
        //    build_vid_media_rtpmap_fmtp(pool, m, local_param, OD_SIP_VID_JAVS);
    }else if (ctx->engine_cfg.protocol_standard == OD_SIP_PROTOCOL_GB28181_2016) {
        if (local_param->video_codec & OD_SIP_VID_H264) {
            if (build_vid_media_rtpmap_fmtp(ctx, m, local_param, OD_SIP_VID_H264) != PJ_SUCCESS) {
                RPTERR("build H264 rtpmap & fmtp error!!!");
            }
        }else if (0) {
            //FIXME: only support H264 now!!! PS,3GP,MPEG4 do not support!!
        }        
    }

    /* Put bandwidth info in media level using bandwidth modifier "TIAS"
    * (RFC3890).
    */
    b = PJ_POOL_ALLOC_T(ctx->pool, pjmedia_sdp_bandw);
    b->modifier = STR_BANDW_TIAS;
    b->value = local_param->vid_bitrate;
    m->bandw[m->bandw_count++] = b;

    if (ctx->engine_cfg.protocol_standard == OD_SIP_PROTOCOL_GB28181_2016) {
        pj_strdup2(ctx->pool, &m->y, build_GB28181_y(ctx, local_param));
        pj_strdup2(ctx->pool, &m->f, build_GB28181_f(ctx, local_param));
    }

    *p_m = m;

    return PJ_SUCCESS;
}


/** Create capacity SDP*/
static pj_status_t media_create_capa_sdp(od_sip_ctx *ctx,
                                   char *buf,
                                   pj_size_t buf_size,
                                   pjmedia_sdp_session **p_sdp,
                                   const od_media_config *local_param)
{
    pjmedia_sdp_session *sdp = NULL;
    pjmedia_sdp_media *m;
    const pj_str_t sess_name = { (char *)"PLAY", 4};
    pj_status_t status;

    PJ_ASSERT_RETURN(ctx->pool && local_param, PJ_EINVAL);

    /* Create and initialize basic SDP session */
    status = create_base_sdp(ctx->pool, &sess_name, &sdp, local_param);
    if (status != PJ_SUCCESS)
        return status;

    /* Audio is first, by convention */
    status = media_create_audio_capa(ctx->pool, &m, local_param);
    if (status != PJ_SUCCESS) {
        RPTERR("create initial audio sdp error!\r\n");
    }else {
        sdp->media[sdp->media_count++] = m;
    }

    /* Add video media */
    status = media_create_video_capa(ctx, &m, local_param);
    if (status != PJ_SUCCESS) {
        RPTERR("create inital video sdp error!\r\n");
    }else { 
        sdp->media[sdp->media_count++] = m;
    }

    *p_sdp = sdp;

    if (buf && buf_size)
        pjmedia_sdp_print(sdp, buf, buf_size);

    return PJ_SUCCESS;
}
                                   

static pj_status_t media_sdp_match_local_offer(pj_pool_t *pool, 
                                                  od_sip_session_ctx *session_ctx)
{
    pjmedia_sdp_neg *neg = NULL;
    pjmedia_sdp_session *local_sdp = session_ctx->session_sdp_neg.local_sdp;
    pjmedia_sdp_session *remote_sdp = session_ctx->session_sdp_neg.remote_sdp;
    const pjmedia_sdp_session *active_local_sdp, *active_remote_sdp;
    char error_str[1024];
    pj_status_t status;
    const pj_bool_t allow_asym = false;

    if (local_sdp == NULL) {
        RPTERR("local_sdp null error!");
        return PJ_EINVAL;
    }

    /* Create negotiator with local offer. */
    status = pjmedia_sdp_neg_create_w_local_offer(pool, local_sdp, &neg);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    /* Give the answer to negotiator. */
    status = pjmedia_sdp_neg_set_remote_answer(pool, neg, remote_sdp);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    /* Negotiate remote answer with local offer */
    status = pjmedia_sdp_neg_negotiate(pool, neg, allow_asym);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    /* Get the local active media */
    status = pjmedia_sdp_neg_get_active_local(neg, &active_local_sdp);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    /* Get the remote active media */
    status = pjmedia_sdp_neg_get_active_remote(neg, &active_remote_sdp);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    session_ctx->session_sdp_neg.active_local_sdp = active_local_sdp;
    session_ctx->session_sdp_neg.active_remote_sdp = active_remote_sdp;

    return PJ_SUCCESS;

exit:
    pjmedia_strerror(status, error_str, sizeof(error_str));
    RPTERR("error: %s", error_str);
    return PJ_EINVAL;
}

/*
* Remote offer sdp negotiate.
*/
static pj_status_t media_sdp_match_remote_offer(od_sip_ctx *ctx, 
                                                  od_sip_session_ctx *session_ctx)
{
    pjmedia_sdp_neg *neg = NULL;
    const pjmedia_sdp_session *active_local_sdp, *active_remote_sdp;
    char error_str[1024];
    const pj_bool_t allow_asym = false;
    pjmedia_sdp_session **local_capa = &session_ctx->session_sdp_neg.local_capa;
    char buf_capa[4096] = {0};
    pj_status_t status;
    
    status = media_create_capa_sdp(ctx, buf_capa, sizeof(buf_capa), 
                                         local_capa, session_ctx->local_capa_config);
    if (status != PJ_SUCCESS) {
        RPTERR("od_media_create_initial_sdp error!");
        return PJ_EINVAL;
    }   

    RPTDBG("capa local:\r\n%s\r\n", buf_capa);

    status = pjmedia_sdp_neg_create_w_remote_offer(ctx->pool, *local_capa, session_ctx->session_sdp_neg.remote_sdp, &neg);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    { //temp debug code
        char tmp[2018] = {0};
        
        if (pjmedia_sdp_print(session_ctx->session_sdp_neg.remote_sdp, tmp, sizeof(tmp)) == -1)
            RPTERR("ERROR:");
        else
            RPTDBG("recv:\r\n %s", tmp);
    }


    /*the negotiator will use the codec order as specified in remote offer*/
    status = pjmedia_sdp_neg_set_prefer_remote_codec_order(neg, PJ_EINVAL);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    /* Negotiate remote answer with local offer */
    status = pjmedia_sdp_neg_negotiate(ctx->pool, neg, allow_asym);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    /* Get the local active media */
    status = pjmedia_sdp_neg_get_active_local(neg, &active_local_sdp);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    /* Get the remote active media */
    status = pjmedia_sdp_neg_get_active_remote(neg, &active_remote_sdp);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    if (1)
    {
        /*get neg result active_local_sdp as local_offer 
        * ang negotiate again to get corect remote SDP result.
        */
        if ((status = pjmedia_sdp_neg_modify_local_offer(ctx->pool, neg, active_local_sdp)) != PJ_SUCCESS)
            goto exit;
        
        if ((status = pjmedia_sdp_neg_set_remote_answer(ctx->pool, neg, active_remote_sdp)) != PJ_SUCCESS)
            goto exit;
        
        if ((status = pjmedia_sdp_neg_set_answer_multiple_codecs(neg, 0)) != PJ_SUCCESS)
            goto exit;
        
        if ((status = pjmedia_sdp_neg_negotiate(ctx->pool, neg, allow_asym)) != PJ_SUCCESS)
            goto exit;

        /* Get the local active media */
        status = pjmedia_sdp_neg_get_active_local(neg, &active_local_sdp);
        if (status != PJ_SUCCESS) {
            goto exit;
        }

        /* Get the remote active media */
        status = pjmedia_sdp_neg_get_active_remote(neg, &active_remote_sdp);
        if (status != PJ_SUCCESS) {
            goto exit;
        }
    }


    session_ctx->session_sdp_neg.active_local_sdp = active_local_sdp;
    session_ctx->session_sdp_neg.active_remote_sdp = active_remote_sdp;

    return PJ_SUCCESS;

exit:
    pjmedia_strerror(status, error_str, sizeof(error_str));
    RPTERR("error: %s", error_str);
    return status;
}


static pj_status_t media_sdp_match(od_sip_ctx *ctx,
                                    char *buf_sdp,
                                    pj_size_t buf_size,
                                    od_sip_session_ctx *session_ctx)
{
    pj_status_t status;
    char error_str[1024];
    od_sip_session_type session_type = session_ctx->session_sdp_neg.session_type;

    RPTDBG("buf_sdp:%s", buf_sdp);
    
    status = pjmedia_sdp_parse(ctx->pool, buf_sdp, buf_size, &session_ctx->session_sdp_neg.remote_sdp);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    status = pjmedia_sdp_validate(session_ctx->session_sdp_neg.remote_sdp);
    if (status != PJ_SUCCESS) {
        goto exit;
    }

    if (session_type == OD_LOCAL_OFFER) {
        if ((status = media_sdp_match_local_offer(ctx->pool, session_ctx)) != PJ_SUCCESS) {
            goto exit;
        }
    }else if (session_type == OD_REMOTE_OFFER) {
        if ((status = media_sdp_match_remote_offer(ctx, session_ctx)) != PJ_SUCCESS) {
            goto exit;
        }
    }else {
        RPTERR("error: unknown session_type!!");
        return PJ_EINVAL;
    }

    {
        char neg_result[2048] = {0};
        pjmedia_sdp_print(session_ctx->session_sdp_neg.active_local_sdp, neg_result, sizeof(neg_result));
        RPTDBG("neg result local:\r\n%s\r\n", neg_result);

        pj_bzero(neg_result, sizeof(neg_result));
        pjmedia_sdp_print(session_ctx->session_sdp_neg.active_remote_sdp, neg_result, sizeof(neg_result));
        RPTDBG("neg result remote:\r\n%s\r\n", neg_result);
    }

    return PJ_SUCCESS;
    
exit:
    pjmedia_strerror(status, error_str, sizeof(error_str));
    RPTERR("error: %s", error_str);
    return status;
}


static void media_neg_get_aud_result_config(const pjmedia_sdp_session *sdp,
                                                od_media_params *param)
{
    pjmedia_sdp_attr *attr;
    pj_status_t status;
    pj_int32_t idx;

    for (idx = 0; idx < sdp->media_count; idx++)
    {
        pjmedia_sdp_media *media = sdp->media[idx];

        if (pj_strcmp(&media->desc.media, &STR_AUDIO))
            continue;

        param->aud.aud_port = media->desc.port;
        param->aud.trans_proto = pjmedia_sdp_transport_get_proto(&media->desc.transport);

        /*get audio rtp payload type*/
        if (media->desc.fmt_count == 1) {
            pj_strltrim(&media->desc.fmt[0]);
            param->aud.aud_pt = pj_strtoul(&media->desc.fmt[0]);
        } else {
            param->aud.aud_port = 0; /*audio port=0 means error!*/
        }

        /*parse "c=" line if exist!*/
        if (media->conn) {
            pjmedia_sdp_conn *conn = media->conn;
            
            if (!pj_strcmp(&conn->addr_type, &STR_IP4)) {
                pj_ansi_snprintf(param->aud.aud_ip, 
                                sizeof(param->aud.aud_ip), 
                                "%.*s",
                                (int)conn->addr.slen,
                                conn->addr.ptr);
            } else if (!pj_strcmp(&conn->addr_type, &STR_IP6)) {
                RPTERR("unsupport IPv6 yet!!!");
            } else {
                RPTERR("unknown addr_type not IPv4 or IPv6!!!");
            }
        }

        /*parse "b=" line if exist!!!*/
        pj_int32_t b_idx;
        for (b_idx = 0; b_idx < media->bandw_count; b_idx++)
        {
            pjmedia_sdp_bandw *bw = media->bandw[b_idx];
            
            if (!pj_strcmp(&bw->modifier, &STR_BANDW_TIAS) 
                    || !pj_strcmp(&bw->modifier, &STR_BANDW_AS) ) {
                param->aud.bitrate = bw->value;
                break;
            } 
        }

        /*parse "a=rtpmap" line*/
        attr = pjmedia_sdp_media_find_attr2(media, pj_strbuf(&STR_RTPMAP), &media->desc.fmt[0]);
        if (attr) {
            pjmedia_sdp_rtpmap rtpmap;
            
            status = pjmedia_sdp_attr_get_rtpmap(attr, &rtpmap);
            if (status != PJ_SUCCESS) {
                RPTERR("rtpmap string error!!");
            } else {
                param->aud.clock_rate = rtpmap.clock_rate;
                pj_ansi_snprintf(param->aud.codec_name,
                                 sizeof(param->aud.codec_name),
                                 "%.*s",
                                 (int)rtpmap.enc_name.slen,
                                 rtpmap.enc_name.ptr);
            }
        }

        /* parse "a=fmtp" line*/
        attr = pjmedia_sdp_media_find_attr2(media, pj_strbuf(&STR_FMTP), &media->desc.fmt[0]);
        if (attr) {
            pjmedia_sdp_fmtp fmtp;

            status = pjmedia_sdp_attr_get_fmtp(attr, &fmtp);
            if (status != PJ_SUCCESS) {
                RPTERR("get aud fmtp error!!");
            } else {
                OD_GET_FMTP_IVAL(param->aud.bitrate, fmtp, STR_BITRATE, 0);
            }
        }

        /* parse "a=sendrecv/sendonly/recvonly/inactive" line*/
        pj_int32_t a_idx;
        for (a_idx = 0; a_idx < media->attr_count; a_idx++)
        {
            if (!pj_strcmp(&media->attr[a_idx]->name, &STR_SENDRECV)
                || !pj_strcmp(&media->attr[a_idx]->name, &STR_SENDONLY)
                || !pj_strcmp(&media->attr[a_idx]->name, &STR_RECVONLY)
                || !pj_strcmp(&media->attr[a_idx]->name, &STR_INACTIVE))
            {
                pj_ansi_snprintf(param->aud.dir, 
                                 sizeof(param->aud.dir), 
                                 "%.*s", 
                                 (int)media->attr[a_idx]->name.slen,
                                 media->attr[a_idx]->name.ptr);
            }
        }
    }
}

static void media_neg_get_GB28181_video_param(pjmedia_sdp_media *offer,
                                                 od_media_params *param)
{
    char result[12][16];
    char *p = &result[0][0];
    char *ptr = NULL;
    int i = 0;

    memset(result, 0, sizeof(result));

    ptr = offer->f.ptr+2; //start parse at v/xx

    /*save all suitable value to result, then parse illegl*/
    *p = '0';
    while(*ptr && i < 12)
    {
        if (*ptr == 'v'
               || *ptr == '/'
               || *ptr == 'a'){
            ptr++;
            p = &result[++i][0];
            *p = '0';
        } else {
            *p++ = *ptr++;
        }
    }

    /*get reso*/
    switch(atoi(&result[1][0]))
    {
        case OD_GB28181_RESO_QCIF:
            param->vid.w = 176;
            param->vid.h = 144;
            break;

        case OD_GB28181_RESO_CIF:
            param->vid.w = 352;
            param->vid.h = 288;
            break;

        case OD_GB28181_RESO_4CIF:
            param->vid.w = 704;
            param->vid.h = 576;
            break;

        case OD_GB28181_RESO_D1:
            param->vid.w = 720;
            param->vid.h = 576;
            break;

        case OD_GB28181_RESO_720P:
            param->vid.w = 1280;
            param->vid.h = 720;
            break;

        case OD_GB28181_RESO_1080P_I:
            param->vid.w = 1920;
            param->vid.h = 1080;
            break;
    }

    param->vid.fps = atoi(&result[2][0]);  
    param->vid.bitrate = atoi(&result[4][0]);
    
    snprintf(param->ssrc, sizeof(param->ssrc), "%s", pj_strbuf(&offer->y));
}


static void media_neg_get_vid_result_config(od_sip_ctx *ctx,
                                               const pjmedia_sdp_session *sdp,
                                               od_media_params *param)
{
    pjmedia_sdp_attr *attr;
    pj_status_t status;
    pj_int32_t idx;
    
    for (idx = 0; idx < sdp->media_count; idx++)
    {
        pjmedia_sdp_media *media = sdp->media[idx];

        if (pj_strcmp(&media->desc.media, &STR_VIDEO))
            continue;

        param->vid.vid_port = media->desc.port;
        param->vid.trans_proto = pjmedia_sdp_transport_get_proto(&media->desc.transport);

        /*get audio rtp payload type*/
        if (media->desc.fmt_count == 1) {
            pj_strltrim(&media->desc.fmt[0]);
            param->vid.vid_pt = pj_strtoul(&media->desc.fmt[0]);
        } else {
            param->vid.vid_port = 0; /*audio port=0 means error!*/
        }

        /*parse "c=" line if exist!*/
        if (media->conn) {
            pjmedia_sdp_conn *conn = media->conn;
            
            if (!pj_strcmp(&conn->addr_type, &STR_IP4)) {
                pj_ansi_snprintf(param->vid.vid_ip, 
                                sizeof(param->vid.vid_ip), 
                                "%.*s",
                                (int)conn->addr.slen,
                                conn->addr.ptr);
            } else if (!pj_strcmp(&conn->addr_type, &STR_IP6)) {
                RPTERR("unsupport IPv6 yet!!!");
            } else {
                RPTERR("unknown addr_type not IPv4 or IPv6!!!");
            }
        }

        /*parse "b=" line if exist!!!*/
        pj_int32_t b_idx;
        for (b_idx = 0; b_idx < media->bandw_count; b_idx++)
        {
            pjmedia_sdp_bandw *bw = media->bandw[b_idx];
            
            if (!pj_strcmp(&bw->modifier, &STR_BANDW_TIAS) 
                    || !pj_strcmp(&bw->modifier, &STR_BANDW_AS) ) {
                param->vid.bitrate = bw->value;
                break;
            } 
        }

        /*parse "a=rtpmap" line*/
        attr = pjmedia_sdp_media_find_attr2(media, pj_strbuf(&STR_RTPMAP), &media->desc.fmt[0]);
        if (attr) {
            pjmedia_sdp_rtpmap rtpmap;
            
            status = pjmedia_sdp_attr_get_rtpmap(attr, &rtpmap);
            if (status != PJ_SUCCESS) {
                RPTERR("rtpmap string error!!");
            } else {
                param->vid.clock_rate = rtpmap.clock_rate;
                pj_ansi_snprintf(param->vid.codec_name,
                                 sizeof(param->vid.codec_name),
                                 "%.*s",
                                 (int)rtpmap.enc_name.slen,
                                 rtpmap.enc_name.ptr);
            }
        }

        if (ctx->engine_cfg.protocol_standard == OD_SIP_PROTOCOL_GJB10) {
            /* parse "a=fmtp" line*/
            attr = pjmedia_sdp_media_find_attr2(media, pj_strbuf(&STR_FMTP), &media->desc.fmt[0]);
            if (attr) {
                pjmedia_sdp_fmtp fmtp;
                uint32_t profile_level_id;
                uint32_t max_mbps;
                uint32_t max_fs;

                status = pjmedia_sdp_attr_get_fmtp(attr, &fmtp);
                if (status != PJ_SUCCESS) {
                    RPTERR("get aud fmtp error!!");
                } else {
                    OD_GET_FMTP_IVAL(profile_level_id, fmtp, STR_PROFILE_LEVEL_ID, 0);
                    OD_GET_FMTP_IVAL(max_mbps, fmtp, STR_MAX_MBPS, 0);
                    OD_GET_FMTP_IVAL(max_fs, fmtp, STR_MAX_FS, 0);

                    pj_int32_t t_idx;
                    for (t_idx = 0; t_idx < PJ_ARRAY_SIZE(h264_standard_table); t_idx++)
                    {
                        if (max_mbps == h264_standard_table[t_idx].max_mbps
                             && max_fs == h264_standard_table[t_idx].max_fs) 
                        {
                             param->vid.w = h264_standard_table[t_idx].reso.w;
                             param->vid.h = h264_standard_table[t_idx].reso.h;
                             param->vid.fps = h264_standard_table[t_idx].fps;
                             param->vid.profile_level_id = profile_level_id;
                        }                    
                    }
                }
            }
        }else if (ctx->engine_cfg.protocol_standard == OD_SIP_PROTOCOL_GB28181_2016) {
            media_neg_get_GB28181_video_param(media, param);
        }


        /* parse "a=sendrecv/sendonly/recvonly/inactive" line*/
        pj_int32_t a_idx;
        for (a_idx = 0; a_idx < media->attr_count; a_idx++)
        {
            if (!pj_strcmp(&media->attr[a_idx]->name, &STR_SENDRECV)
                || !pj_strcmp(&media->attr[a_idx]->name, &STR_SENDONLY)
                || !pj_strcmp(&media->attr[a_idx]->name, &STR_RECVONLY)
                || !pj_strcmp(&media->attr[a_idx]->name, &STR_INACTIVE))
            {
                pj_ansi_snprintf(param->vid.dir, 
                                 sizeof(param->vid.dir), 
                                 "%.*s", 
                                 (int)media->attr[a_idx]->name.slen,
                                 media->attr[a_idx]->name.ptr);
            }
        }

        /*only support signel video, so break*/
        break;
    }
}

static pj_status_t media_neg_get_result_config(od_sip_ctx *ctx, 
                                         const pjmedia_sdp_session *sdp,
                                         od_media_params *param) 
{ 
    pjmedia_sdp_conn *conn;
    pjmedia_sdp_attr *attr;
    pj_int32_t b_idx;

    /*parse connection info ("c=" line), get send IP.*/
    conn = sdp->conn;
    if (conn != NULL) {
        if (!pj_strcmp(&conn->addr_type, &STR_IP4)) {
            pj_ansi_snprintf(param->vid.vid_ip, 
                            sizeof(param->vid.vid_ip), 
                            "%.*s",
                            (int)conn->addr.slen,
                            conn->addr.ptr);

            pj_ansi_snprintf(param->aud.aud_ip, 
                            sizeof(param->aud.aud_ip), 
                            "%.*s",
                            (int)conn->addr.slen,
                            conn->addr.ptr);
        } else if (!pj_strcmp(&conn->addr_type, &STR_IP6)) {
            RPTERR("unsupport IPv6 yet!!!");
        } else {
            RPTERR("unknown addr_type not IPv4 or IPv6!!!");
        }
    }

    /*parse "b=" line if exist!!!*/
    for (b_idx = 0; b_idx < sdp->bandw_count; b_idx++)
    {
        pjmedia_sdp_bandw *bw = sdp->bandw[b_idx];
        
        if (!pj_strcmp(&bw->modifier, &STR_BANDW_TIAS) 
                || !pj_strcmp(&bw->modifier, &STR_BANDW_AS) ) {
            param->sys_bandw = bw->value;
            break;
        } 
    }

   /* parse a=rtpport-mux attr if exist!!
    * and get muxid, muxid from 0 to 0xFFFFFFFF .
    */
    attr = pjmedia_sdp_attr_find2(sdp->attr_count, sdp->attr, pj_strbuf(&STR_RTP_PORTMUX), NULL);
    if (attr) {
        attr = pjmedia_sdp_attr_find2(sdp->attr_count, sdp->attr, pj_strbuf(&STR_MUXID), NULL);
        if (attr) {
            pj_str_t tmp_val = attr->value;

            pj_strltrim(&tmp_val);
            param->rtpport_mux.muxid = pj_strtoul(&tmp_val);
            param->rtpport_mux.flg   = true;
        }
    }

    /* parse m=audio line */
    media_neg_get_aud_result_config(sdp, param);

    /* parse "m=video" line*/
    media_neg_get_vid_result_config(ctx, sdp, param);

    return PJ_SUCCESS;
}


pj_status_t od_media_create_initial_sdp(od_sip_ctx *ctx,
                                        char *buf,
                                        pj_size_t buf_size,
                                        pjmedia_sdp_session **p_sdp,
                                        const od_media_config *local_param)
{
    pjmedia_sdp_session *sdp = NULL;
    pjmedia_sdp_media *m;
    const pj_str_t sess_name = { (char *)"PLAY", 4};
    pj_status_t status;

    PJ_ASSERT_RETURN(ctx && local_param, PJ_EINVAL);

    /* Create and initialize basic SDP session */
    status = create_base_sdp(ctx->pool, &sess_name, &sdp, local_param);
    if (status != PJ_SUCCESS) {
        RPTERR("SDP create base infomation error!!!");
        return status;
    }

    /* Audio is first, by convention */
    status = create_initial_audio_sdp(ctx->pool, &m, local_param);
    if (status != PJ_SUCCESS) {
        RPTWRN("create initial audio sdp error!\r\n");
    }else {
        sdp->media[sdp->media_count++] = m;
    }

    /* Add video media */
    status = create_initial_video_sdp(ctx, &m, local_param);
    if (status != PJ_SUCCESS) {
        RPTWRN("create inital video sdp error!\r\n");
    }else { 
        sdp->media[sdp->media_count++] = m;
    }

    if (p_sdp)
        *p_sdp = sdp;

    if (buf && buf_size) {
        if (pjmedia_sdp_print(sdp, buf, buf_size) < 0) {
            RPTERR("pjmedia_sdp_print error, buf too short!!");
            return PJ_ETOOSMALL;
        }
    }
        
    return PJ_SUCCESS;
}


pj_status_t od_media_sdp_negotiate(od_sip_ctx *ctx,
                                     char *remote_sdp,
                                     pj_size_t remote_sdp_size,
                                     od_sip_session_ctx *session_ctx)
{
    pj_status_t status;
    const pjmedia_sdp_session *active_local_sdp, *active_remote_sdp;
    
    PJ_ASSERT_RETURN(ctx && remote_sdp && session_ctx, PJ_EINVAL);

    status = media_sdp_match(ctx, remote_sdp, remote_sdp_size, session_ctx);
    if (status != PJ_SUCCESS)
    {
        RPTERR("od_media_sdp_macth() error!");
        return PJ_EINVAL;
    }

    active_local_sdp = session_ctx->session_sdp_neg.active_local_sdp;
    active_remote_sdp = session_ctx->session_sdp_neg.active_remote_sdp;

    media_neg_get_result_config(ctx, active_local_sdp, &session_ctx->decode_media);
    session_ctx->decode_media.cfg_type = PARAM_TYPE_DECODE;
    
    media_neg_get_result_config(ctx, active_remote_sdp, &session_ctx->encode_media);
    session_ctx->encode_media.cfg_type = PARAM_TYPE_ENCODE;

    if (session_ctx->decode_media.rtpport_mux.flg != 
            session_ctx->encode_media.rtpport_mux.flg) {
        session_ctx->decode_media.rtpport_mux.flg = 0;
        session_ctx->encode_media.rtpport_mux.flg = 0;
    }

    /*This media negotiation failed as long as one audio port is zero*/
    if (session_ctx->decode_media.aud.aud_port == 0
         || session_ctx->encode_media.aud.aud_port == 0) {
        session_ctx->decode_media.aud.aud_port = 0;
        session_ctx->encode_media.aud.aud_port = 0;
    }

    /*This media negotiation failed as long as one video port is zero*/
    if (session_ctx->decode_media.vid.vid_port == 0
         || session_ctx->encode_media.vid.vid_port == 0) {
        session_ctx->decode_media.vid.vid_port = 0;
        session_ctx->encode_media.vid.vid_port = 0;
    }   

    return PJ_SUCCESS;
}


int od_media_sdp_print(const pjmedia_sdp_session *sdp, 
				         char *buf, 
				         pj_size_t size)
{
    PJ_ASSERT_RETURN(sdp && buf, PJ_EINVAL);

    return pjmedia_sdp_print(sdp, buf, size);
}

/*_*/

